<?php

lib('io.*');

class ModRewrite {
    protected $ifmodule = array("", '');
    protected $rules = array();
    protected $is_bottom;
    protected $bottom = array();
    protected $options = array();
    protected $source;

    protected $base;
    protected $engine = 'on';
    protected $lock;
    protected $log;
    protected $log_level = 0;
    protected $map;
    protected $unrec;

    // String, .htacess file name
    private $file;

    function __construct(InputStream $in) {
        if (!$in->is_opened()) $in->open();
        $this->source = $in;
        $this->load_file();
    }

    function load_file() {
        try {
            while (true) {
                $line = $this->source->read_line();
                $this->parse_line($line);
            }
        }
        catch(IOException $e) {

        }
    }

    protected function is_rewrite_line($line) {
        return (stripos($line, "rewrite") > -1);
    }

    protected function parse_line($line) {
        if ($this->parse_base($line)) return true;
        if ($this->parse_cond($line)) return true;
        if ($this->parse_engine($line)) return true;
        if ($this->parse_lock($line)) return true;
        if ($this->parse_log($line)) return true;
        if ($this->parse_log_level($line)) return true;
        if ($this->parse_map($line)) return true;
        if ($this->parse_options($line)) return true;
//        if ($this->is_modtag($line)) return true;
        if ($this->parse_rule($line)) return true;
        if ($this->is_bottom($line)) return true;
        if (trim($line) == '') return true;
        $this->unrec[] = $line;
        return false;
    }

    protected function parse_base($line) {
        if (stripos($line, 'rewritebase') > -1) {
            $this->base = trim(str_ireplace('rewritebase', '', $line));
            return true;
        }
        return false;
    }

    protected function parse_cond($line) {
        if (stripos($line, 'rewritecond') > -1) {
            $cond = trim(str_ireplace('rewritecond', '', $line));
            $cond = explode(' ', $cond);
            $cond = array(
                'subject' 			=>		$cond[0],
                'replacement'		=>		$cond[1],
                'flags'				=>		@$cond[2]
            );
            if (!$this->is_bottom) {
                $rid = count($this->rules) - 1;
                if ($rid < 0 || @key_exists('subject', $this->rules[$rid])) {
                    $rid++;
                    $this->rules[$rid] = array(
                        'conds' => array()
                    );
                }
                $cid = count($this->rules[$rid]['conds']);
                $this->rules[$rid]['conds'][$cid] = $cond;
            }
            else {
                $rid = count($this->bottom) - 1;
                if ($rid < 0 || @key_exists('subject', $this->bottom[$rid])) {
                    $rid++;
                    $this->bottom[$rid] = array(
                        'conds' => array()
                    );
                }
                $cid = count($this->bottom[$rid]['conds']);
                $this->bottom[$rid]['conds'][$cid] = $cond;
            }

            return true;
        }
        return false;
    }

    protected function parse_engine($line) {
        if (stripos($line, 'rewriteengine') > -1) {
            $this->engine = trim(str_ireplace('rewriteengine', '', $line));
            return true;
        }
        return false;
    }

    protected function parse_lock($line) {
        if (stripos($line, 'rewritelock') > -1) {
            $this->lock = trim(str_ireplace('rewritelock', '', $line));
            return true;
        }
        return false;

    }

    protected function parse_log($line) {
        if (stripos($line, 'rewritelog') > -1) {
            $this->log = trim(str_ireplace('rewritelog', '', $line));
            return true;
        }
        return false;
    }

    protected function parse_log_level($line) {
        if (stripos($line, 'rewriteloglevel') > -1) {
            $this->log_level = trim(str_ireplace('rewriteloglevel', '', $line));
            return true;
        }
        return false;
    }

    protected function parse_map($line) {
        if (stripos($line, 'rewritemap') > -1) {
            $this->map = trim(str_ireplace('rewritemap', '', $line));
            return true;
        }
        return false;
    }

    protected function parse_options($line) {
        if (stripos($line, 'rewriteoptions') > -1) {
            $this->options[] = trim(str_ireplace('rewriteoptions', '', $line));
            return true;
        }
        return false;
    }

    protected function is_bottom($line) {
        if (stripos($line, '#bottom') > -1) {
            $this->is_bottom = true;
            return true;
        }
        return false;
    }

    protected function is_modtag($line) {
        if (stripos($line, 'ifmodule') > -1) {
            if (stripos($line, 'mod_rewrite') > -1) return true;
        }
        return false;
    }

    protected function parse_rule($line) {
        if (stripos($line, 'rewriterule') > -1) {
            $cond = trim(str_ireplace('rewriterule', '', $line));
            $cond = explode(' ', $cond);
            $rule['subject']                    =		$cond[0];
            $rule['replacement']		=		$cond[1];
            $rule['flags']			=		@$cond[2];

            if (!$this->is_bottom) {
                $rid = count($this->rules)-1;
                if ($rid < 0) $rid = 0;
                elseif (key_exists('subject', $this->rules[$rid])) {
                    $rid++;
                }
                $this->rules[$rid] = $rule;
            } else {
                $rid = count($this->bottom)-1;
                if ($rid < 0) $rid = 0;
                elseif (key_exists('subject', $this->bottom[$rid])) {
                    $rid++;
                }
                $this->bottom[$rid] = $rule;
            }
            return true;
        }
        return false;
    }


    public function get_rule($id) {
        if (!key_exists($id, $this->rules)) throw new XMLException(QConst::X_UNDEFINED, "$id: No such rule.");
        $rule = $this->rules[$id];
        unset($rule['conds']);
        return $rule;
    }

    public function get_conds($rule_id) {
        if (!key_exists($rule_id, $this->rules)) throw new XMLException(QConst::X_UNDEFINED, "$rule_id: No such rule.");
        return $this->rules[$rule_id]['conds'];
    }

    public function add_rule($subject, $replacement, $flags) {
        $this->rules[] = array(
            'subject' 			=> $subject,
            'replacement' 		=> $replacement,
            'flags'			=> $flags
        );
    }

    public function get_rules_count() {
        return count($this->rules);
    }

    public function get_rule_subject($rid) {
        return $this->rules[$rid]['subject'];
    }
    public function get_rule_replacement($rid) {
        return $this->rules[$rid]['replacement'];
    }
    public function get_rule_flags($rid) {
        return $this->rules[$rid]['flags'];
    }

    public function get_rule_conds_count($ruleid) {
        return count($this->rules[$rid]['conds']);
    }

    public function save(OutputStream $out) {
        if (!$out->is_opened()) $out->rewrite();
        // saving unrecognized strings
        for ($x=0;$x<count($this->unrec);$x++) {
            $out->writeln($this->unrec[$x]);
        }
        // writing open tag
        $out->writeln($this->ifmodule[0]);

        // writing Base
        if ($this->base) $out->writeln('RewriteBase '.$this->base);

        // writing Engine
        if ($this->engine) $out->writeln('RewriteEngine '.$this->engine);

        // writing lock
        if ($this->lock) $out->writeln('RewriteLock '.$this->lock);

        // writing log
        if ($this->log) $out->writeln('RewriteLog '.$this->log);

        // writing loglevel
        if ($this->log_level) $out->writeln('RewriteLogLevel '.$this->log_level);

        // writing map
        if ($this->map) $out->writeln('RewriteMap '.$this->map);

        // writing options
        if ($this->options) $out->writeln('RewriteOptions '.$this->options);

        // writing rules
        for($i=0;$i<count($this->rules);$i++) {
            $out->writeln("");
            // writing rule conditions
            for($y=0;$y<@count($this->rules[$i]['conds']);$y++) {
                $cond = $this->rules[$i]['conds'][$y];
                $ln = 'RewriteCond '.$cond['subject'].' '.$cond['replacement'];
                if ($cond['flags']) $ln .= ' '.$cond['flags'];
                $out->writeln($ln);
            }
            // writing rule
            $ln = 'RewriteRule '.$this->rules[$i]['subject'].' '.$this->rules[$i]['replacement'];
            if ($this->rules[$i]['flags']) $ln.= ' '.$this->rules[$i]['flags'];
            $out->writeln($ln);
        }

        // writing bottom rules
        if ($this->is_bottom) {
            $out->writeln('#bottom');
            for($i=0;$i<count($this->bottom);$i++) {
                $out->writeln("");
                // writing rule conditions
                for($y=0;$y<@count($this->bottom[$i]['conds']);$y++) {
                    $cond = $this->bottom[$i]['conds'][$y];
                    $ln = 'RewriteCond '.$cond['subject'].' '.$cond['replacement'];
                    if ($cond['flags']) $ln .= ' '.$cond['flags'];
                    $out->writeln($ln);
                }
                // writing rule
                $ln = 'RewriteRule '.$this->bottom[$i]['subject'].' '.$this->bottom[$i]['replacement'];
                if ($this->bottom[$i]['flags']) $ln.= ' '.$this->bottom[$i]['flags'];
                $out->writeln($ln);
            }
        }

        // writing tag
        $out->writeln($this->ifmodule[1]);
    }

    function search_rule($subject) {
        for ($i=0; $i <count($this->rules); $i++) {
            if ($this->rules[$i]['subject'] == $subject) return $i;
        }
        return -1;
    }

    function get_all_subjects() {
        $subj = array();
        for ($i=0; $i <count($this->rules); $i++) {
            $subj[$i] = $this->rules[$i]['subject'];
        }
        return $subj;
    }

    function get_all_rules() {
        $subj = array();
        for ($i=0; $i <count($this->rules); $i++) {
            $subj[$i] = $this->rules[$i];
        }
        return $subj;
    }
}
